Fix ToJsonObject serialization failure with anonymous types and add support for custom JsonSerializerOptions (#1113)

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
Co-authored-by: eiriktsarpalis <2813363+eiriktsarpalis@users.noreply.github.com>
Co-authored-by: Stephen Toub <stoub@microsoft.com>
This commit is contained in:
Copilot 2026-01-20 11:21:11 +02:00 • committed by GitHub
commit 46d30444a8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 366 additions and 42 deletions

View file

@ -73,16 +73,19 @@ public abstract partial class McpServer : McpSession
/// </summary>
/// <param name="messages">The messages to send as part of the request.</param>
/// <param name="chatOptions">The options to use for the request, including model parameters and constraints.</param>
/// <param name="serializerOptions">The <see cref="JsonSerializerOptions"/> to use for serializing user-provided objects. If <see langword="null"/>, <see cref="McpJsonUtilities.DefaultOptions"/> is used.</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 chat response from the model.</returns>
/// <exception cref="ArgumentNullException"><paramref name="messages"/> is <see langword="null"/>.</exception>
/// <exception cref="InvalidOperationException">The client does not support sampling.</exception>
/// <exception cref="McpException">The request failed or the client returned an error response.</exception>
public async Task<ChatResponse> SampleAsync(
IEnumerable<ChatMessage> messages, ChatOptions? chatOptions = default, CancellationToken cancellationToken = default)
IEnumerable<ChatMessage> messages, ChatOptions? chatOptions = default, JsonSerializerOptions? serializerOptions = null, CancellationToken cancellationToken = default)
{
Throw.IfNull(messages);
serializerOptions ??= McpJsonUtilities.DefaultOptions;
StringBuilder? systemPrompt = null;
if (chatOptions?.Instructions is { } instructions)
@ -148,7 +151,7 @@ public abstract partial class McpServer : McpSession
Name = af.Name,
Description = af.Description,
InputSchema = af.JsonSchema,
Meta = af.AdditionalProperties.ToJsonObject(),
Meta = af.AdditionalProperties.ToJsonObject(serializerOptions),
});
}
}
@ -172,13 +175,13 @@ public abstract partial class McpServer : McpSession
Temperature = chatOptions?.Temperature,
ToolChoice = toolChoice,
Tools = tools,
Meta = chatOptions?.AdditionalProperties?.ToJsonObject(),
Meta = chatOptions?.AdditionalProperties?.ToJsonObject(serializerOptions),
}, cancellationToken).ConfigureAwait(false);
List<AIContent> responseContents = [];
foreach (var block in result.Content)
{
if (block.ToAIContent() is { } content)
if (block.ToAIContent(serializerOptions) is { } content)
{
responseContents.Add(content);
}
@ -202,13 +205,14 @@ public abstract partial class McpServer : McpSession
/// <summary>
/// Creates an <see cref="IChatClient"/> wrapper that can be used to send sampling requests to the client.
/// </summary>
/// <param name="serializerOptions">The <see cref="JsonSerializerOptions"/> to use for serialization. If <see langword="null"/>, <see cref="McpJsonUtilities.DefaultOptions"/> is used.</param>
/// <returns>The <see cref="IChatClient"/> that can be used to issue sampling requests to the client.</returns>
/// <exception cref="InvalidOperationException">The client does not support sampling.</exception>
public IChatClient AsSamplingChatClient()
public IChatClient AsSamplingChatClient(JsonSerializerOptions? serializerOptions = null)
{
ThrowIfSamplingUnsupported();
return new SamplingChatClient(this);
return new SamplingChatClient(this, serializerOptions ?? McpJsonUtilities.DefaultOptions);
}
/// <summary>Gets an <see cref="ILogger"/> on which logged messages will be sent as notifications to the client.</summary>
@ -520,13 +524,14 @@ public abstract partial class McpServer : McpSession
}
/// <summary>Provides an <see cref="IChatClient"/> implementation that's implemented via client sampling.</summary>
private sealed class SamplingChatClient(McpServer server) : IChatClient
private sealed class SamplingChatClient(McpServer server, JsonSerializerOptions serializerOptions) : IChatClient
{
private readonly McpServer _server = server;
private readonly JsonSerializerOptions _serializerOptions = serializerOptions;
/// <inheritdoc/>
public Task<ChatResponse> GetResponseAsync(IEnumerable<ChatMessage> messages, ChatOptions? chatOptions = null, CancellationToken cancellationToken = default) =>
_server.SampleAsync(messages, chatOptions, cancellationToken);
_server.SampleAsync(messages, chatOptions, _serializerOptions, cancellationToken);
/// <inheritdoc/>
async IAsyncEnumerable<ChatResponseUpdate> IChatClient.GetStreamingResponseAsync(

View file

@ -160,7 +160,7 @@ public sealed class McpServerOptions
/// The default maximum number of tokens to use for sampling requests. The default value is 1000 tokens.
/// </value>
/// <remarks>
/// This value is used in <see cref="McpServer.SampleAsync(IEnumerable{Microsoft.Extensions.AI.ChatMessage}, Microsoft.Extensions.AI.ChatOptions?, CancellationToken)"/>
/// This value is used in <see cref="McpServer.SampleAsync(IEnumerable{Microsoft.Extensions.AI.ChatMessage}, Microsoft.Extensions.AI.ChatOptions?, System.Text.Json.JsonSerializerOptions?, CancellationToken)"/>
/// when <see cref="Microsoft.Extensions.AI.ChatOptions.MaxOutputTokens"/> is not set in the request options.
/// </remarks>
public int MaxSamplingOutputTokens { get; set; } = 1000;

View file

@ -99,7 +99,7 @@ namespace ModelContextProtocol.Server;
/// </item>
/// <item>
/// <term><see cref="AIContent"/></term>
/// <description>Converted to a single <see cref="ContentBlock"/> object using <see cref="AIContentExtensions.ToContentBlock(AIContent)"/>.</description>
/// <description>Converted to a single <see cref="ContentBlock"/> object using <see cref="AIContentExtensions.ToContentBlock(AIContent, JsonSerializerOptions)"/>.</description>
/// </item>
/// <item>
/// <term><see cref="string"/></term>
@ -111,7 +111,7 @@ namespace ModelContextProtocol.Server;
/// </item>
/// <item>
/// <term><see cref="IEnumerable{AIContent}"/> of <see cref="AIContent"/></term>
/// <description>Each <see cref="AIContent"/> is converted to a <see cref="ContentBlock"/> object using <see cref="AIContentExtensions.ToContentBlock(AIContent)"/>.</description>
/// <description>Each <see cref="AIContent"/> is converted to a <see cref="ContentBlock"/> object using <see cref="AIContentExtensions.ToContentBlock(AIContent, JsonSerializerOptions)"/>.</description>
/// </item>
/// <item>
/// <term><see cref="IEnumerable{ContentBlock}"/> of <see cref="ContentBlock"/></term>

View file

@ -90,7 +90,7 @@ namespace ModelContextProtocol.Server;
/// </item>
/// <item>
/// <term><see cref="AIContent"/></term>
/// <description>Converted to a single <see cref="ContentBlock"/> object using <see cref="AIContentExtensions.ToContentBlock(AIContent)"/>.</description>
/// <description>Converted to a single <see cref="ContentBlock"/> object using <see cref="AIContentExtensions.ToContentBlock(AIContent, JsonSerializerOptions)"/>.</description>
/// </item>
/// <item>
/// <term><see cref="string"/></term>
@ -106,7 +106,7 @@ namespace ModelContextProtocol.Server;
/// </item>
/// <item>
/// <term><see cref="IEnumerable{AIContent}"/> of <see cref="AIContent"/></term>
/// <description>Each <see cref="AIContent"/> is converted to a <see cref="ContentBlock"/> object using <see cref="AIContentExtensions.ToContentBlock(AIContent)"/>.</description>
/// <description>Each <see cref="AIContent"/> is converted to a <see cref="ContentBlock"/> object using <see cref="AIContentExtensions.ToContentBlock(AIContent, JsonSerializerOptions)"/>.</description>
/// </item>
/// <item>
/// <term><see cref="IEnumerable{Content}"/> of <see cref="ContentBlock"/></term>