Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Jan 30, 2026

Passing JsonSerializerOptions without a TypeInfoResolver to McpServerTool.Create throws InvalidOperationException. Users expect this to work like standard JsonSerializer.Serialize methods which silently populate the resolver.

Based on review feedback from #1203 by @eiriktsarpalis.

Changes

  • Add GetSerializerOptions helper in AIFunctionMcpServerTool that populates TypeInfoResolver from McpJsonUtilities.DefaultOptions when null, then calls MakeReadOnly()
  • Add test verifying options without resolver work correctly

Example

// This now works (previously threw)
var options = new JsonSerializerOptions 
{ 
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase 
};

McpServerTool.Create((string name) => $"Hello, {name}!", 
    new() { SerializerOptions = options });

// options.TypeInfoResolver is now populated
// options.IsReadOnly is now true

Co-authored-by: GeorgiPopovIT 69841500+GeorgiPopovIT@users.noreply.github.com

Original prompt

Please create a new PR with the commits from this PR (so the credit is still given) but then revising them based on the feedback in #1203 (comment)


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Co-authored-by: GeorgiPopovIT <69841500+GeorgiPopovIT@users.noreply.github.com>

Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
Copilot AI changed the title [WIP] Revise commits based on PR feedback Fix JsonSerializerOptions without TypeInfoResolver Jan 30, 2026
Copilot AI requested a review from stephentoub January 30, 2026 16:36
if (customOptions.TypeInfoResolver is null)
{
Debug.Assert(!customOptions.IsReadOnly, "If no resolver is present then the options must still be editable.");
customOptions.TypeInfoResolver = McpJsonUtilities.DefaultOptions.TypeInfoResolver;
Copy link
Contributor

Choose a reason for hiding this comment

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

@eiriktsarpalis presumably there's a race condition here, where by the time we go to set TypeInfoResolver, it's already been set and/or the instance has already been made read-only? Is the right answer for that to a) just accept/document that concurrent use may result in such overwrites and b) catch whatever exception would emerge when trying to set this on an already read-only instance?

Copy link
Member

Choose a reason for hiding this comment

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

It's possible, although modifying the JSO while concurrently passing it to a serializer is a known source of errors and is not recommended. What bothers me most though is that this method could race with, say, JsonSerializer.Serialize which will try to assign it the default reflection-based resolver, or any other method that could try to set its own resolver following this convention.

Copy link
Contributor

Choose a reason for hiding this comment

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

What do you think we should do, @eiriktsarpalis ?

Is going back to the original solution of creating a clone JSO the least-bad option?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants